#include <stdio.h>
#include "../lib/lua/src/lua.h"
#include "../lib/lua/src/lualib.h"
#include "../lib/lua/src/lauxlib.h"

void lua_check(int is_failed){
    if (is_failed){
        printf("Lua execute failed.\n");
    }
}

// 执行一个Lua文件
void lua_example_dofile(void){
    // 得到State
    lua_State* L = luaL_newstate();
    // 这行用来打开LUA的标准库
    luaL_openlibs(L);

    // 执行文件
    lua_check(luaL_dofile(L, "./scripts/factorial.lua"));
    // 需要在最后释放
    lua_close(L);
}

// 
void lua_example_getvar(void){
    lua_State* L = luaL_newstate();

    // 执行一段LUA代码
    lua_check(luaL_dostring(L, "some_var = 486"));
    // 得到全局变量
    // 这里需要解释一下LUA的机制，他拥有一个栈，这个指令相当于把这个值推入到栈中。
    lua_getglobal(L, "some_var");
    // 得到数字。
    // 这个tonumber的意思就是从栈中取一个值出来。(但是这不是POP，因为POP是要删除项目，这里不删除)
    // 这个-1表示最后一个值。同样，-2,-3,-4,-5表示栈中依次倒数的值。
    lua_Number some_var_in_c = lua_tonumber(L, -1);

    printf("The value of some_var inside C is: %d\n", (int)some_var_in_c);

    lua_close(L);
}

void lua_example_stack(void){
    lua_State* L = luaL_newstate();
    lua_pushnumber(L, 286); // stack[1] or stack[-3]
    lua_pushnumber(L, 386); // stack[2] or stack[-2]
    lua_pushnumber(L, 486); // stack[3] or stack[-1]
    lua_Number element;
    element = lua_tonumber(L, -1);
    printf("The last (in position 3) is: %d\n", (int)element);

    // 这里要移除标号为2的栈数值，也就是386。这里有奇怪的地方了，这个标记都是从1开始的。
    // 索引是从1开始（而且0的确在这里不合适）
    lua_remove(L, 2);
    // 因为前面已经把位于2的386删除了，所以这里得到的是486，因为最后一位提上来（或者是下去）了。
    // 所以这里的栈是自由提取的，也不一定按先进后出访问。可以直接使用索引。
    element = lua_tonumber(L, 2);
    printf("The value (in position 2) is: %d\n", (int)element);

    lua_close(L);
}

void lua_example_call_lua_function(void){
    lua_State* L = luaL_newstate();

    // 这里处理错误
    if (luaL_dofile(L, "./scripts/pythagoras.lua") != LUA_OK){
        // 错误也是放在栈上，所以从栈上取
        luaL_error(L, "Error: %s\n", lua_tostring(L, -1));
    }

    lua_getglobal(L, "pythagoras");
    if (lua_isfunction(L, -1)){
        // LUA中调用函数怎么取得参数？就是通过栈获得的，所以需要将值塞入栈中
        lua_pushnumber(L, 3); // = a
        lua_pushnumber(L, 4); // = b

        // pcall = protected call
        // 第2个参数是函数接受的参数数量
        const int NUM_ARGS = 2;
        // 第3个参数是函数返回参数的数量
        const int NUM_RETURNS = 1;
        // 最后一个参数是用于放置错误信息的地方，如果不接受信息，设置为0
        if (lua_pcall(L, NUM_ARGS, NUM_RETURNS, 0) != LUA_OK){
            luaL_error(L, "Error: %s\n", lua_tostring(L, -1));
        }else{
            lua_Number result = lua_tonumber(L, -1);
            printf("Result of calling function: %f\n", (float)result);
        }

    }

    lua_close(L);
}

// 在LUA中调用C函数的规则：
// 返回值必须是int，这个int不是代表结果，而是返回的参数的个数
// 而参数必须是lua_State*。
int native_pythagoras(lua_State* L){
    // 因为是从左到右入栈的，所以第一个应该是b
    lua_Number b = lua_tonumber(L, -1);
    lua_Number a = lua_tonumber(L, -2);
    // 将结果入栈
    lua_Number result = a * a + b * b;
    lua_pushnumber(L, result);
    // 返回返回值的个数
    return 1;
}

void lua_example_call_c_function(void){
    lua_State* L = luaL_newstate();

    // 将函数入栈
    lua_pushcfunction(L, native_pythagoras);
    // 将C函数赋值给native_pythagoras这个名字
    lua_setglobal(L, "native_pythagoras");

    // 这里处理错误
    if (luaL_dofile(L, "./scripts/pythagoras_native.lua") != LUA_OK){
        // 错误也是放在栈上，所以从栈上取
        luaL_error(L, "Error: %s\n", lua_tostring(L, -1));
    }
    lua_getglobal(L, "pythagoras");
    if (lua_isfunction(L, -1)){
        // LUA中调用函数怎么取得参数？就是通过栈获得的，所以需要将值塞入栈中
        lua_pushnumber(L, 3); // = a
        lua_pushnumber(L, 4); // = b

        // pcall = protected call
        // 第2个参数是函数接受的参数数量
        const int NUM_ARGS = 2;
        // 第3个参数是函数返回参数的数量
        const int NUM_RETURNS = 1;
        // 最后一个参数是用于放置错误信息的地方，如果不接受信息，设置为0
        if (lua_pcall(L, NUM_ARGS, NUM_RETURNS, 0) != LUA_OK){
            luaL_error(L, "Error: %s\n", lua_tostring(L, -1));
        }else{
            lua_Number result = lua_tonumber(L, -1);
            printf("Result of calling by native function: %f\n", (float)result);
        }

    }

    lua_close(L);
}

typedef struct rectangle2d {
    int x;
    int y;
    int width;
    int height;
} rectangle;

int create_rectangle(lua_State* L){
    // 通过lua_newuserdata创建一个对象
    // 这里不用入栈什么的，表示lua_newuserdata这个就已经完成入栈操作了。
    rectangle* rect = (rectangle*)lua_newuserdata(L, sizeof(rectangle));
    rect->x = 0;
    rect->y = 0;
    rect->width = 0;
    rect->height = 0;
    return 1;    
}

int change_rectangle_size(lua_State* L){
    // 同理，从左向右入栈的，在最左边的在最里面
    rectangle* rect = lua_touserdata(L, -3);
    rect->width = (int)lua_tonumber(L, -2);
    rect->height = (int)lua_tonumber(L, -1);
    // 不返回值
    return 0;
}

void lua_example_userdata(void){
    lua_State* L = luaL_newstate();

    // 将本地的create_rectangle注册到lua
    lua_pushcfunction(L, create_rectangle);
    lua_setglobal(L, "create_rectangle");
    lua_pushcfunction(L, change_rectangle_size);
    lua_setglobal(L, "change_rectangle_size");

    if (luaL_dofile(L, "./scripts/rectangle.lua") != LUA_OK){
        luaL_error(L, "Error: %s\n", lua_tostring(L, -1));
    }

    lua_getglobal(L, "square");
    if (lua_isuserdata(L, -1)){
        // 从栈上取出
        rectangle* r = (rectangle*)lua_touserdata(L, -1);
        printf("We go back a rectangle from Lua, width:%d, height:%d\n", r->width, r->height);
    }else{
        printf("We did not get a rectangle from Lua.");
    }

    lua_close(L);
}

void lua_example_table(void){
    lua_State* L = luaL_newstate();

    if (luaL_dofile(L, "./scripts/configtable.lua") != LUA_OK){
        luaL_error(L, "Error: %s\n", lua_tostring(L, -1));
    }

    // 这里是把表放到栈中
    lua_getglobal(L, "config_table");
    if (lua_istable(L, -1)){
        // 注意这里，这是当栈上有一个表的时候，使用get_field把表中的一个值推到栈中，这样从栈中访问值。
        lua_getfield(L, -1, "window_width");
        printf("The window width: %s\n", lua_tostring(L, -1));
    }

    lua_close(L);
}

void execute_lua_example(void){
    printf("Hello from c.\n");

    lua_example_dofile();
    lua_example_getvar();
    lua_example_stack();
    lua_example_call_lua_function();
    lua_example_call_c_function();
    lua_example_userdata();
    lua_example_table();
}